/*
 * Copyright 2017 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.blockly.ohos.ui.mutator;

import com.google.blockly.ohos.ResourceTable;
import com.google.blockly.ohos.adapter.RecyclerAdapter;
import com.google.blockly.ohos.control.BlocklyController;
import com.google.blockly.ohos.control.NameManager;
import com.google.blockly.ohos.control.ProcedureManager;
import com.google.blockly.ohos.control.ProcedureManager.ArgumentIndexUpdate;
import com.google.blockly.ohos.ui.MutatorFraction;
import com.google.blockly.model.Mutator;
import com.google.blockly.model.ProcedureInfo;
import com.google.blockly.model.mutator.ProcedureDefinitionMutator;
import com.google.blockly.utils.LogUtils;
import ohos.agp.components.*;
import ohos.agp.window.dialog.CommonDialog;
import ohos.agp.window.dialog.IDialog;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Standard UI Fraction for editing the {@link ProcedureDefinitionMutator} attached to
 * {@code procedures_defnoreturn} and {@code procedures_defreturn} blocks.
 */
public class ProcedureDefinitionMutatorFraction extends MutatorFraction {
    private static final String TAG = "ProcedureDefinitionMutatorFraction";

    public static final Factory FACTORY =
            new Factory<ProcedureDefinitionMutatorFraction>() {
                @Override
                public ProcedureDefinitionMutatorFraction newMutatorFraction(Context context, Mutator mutator) {
                    ProcedureDefinitionMutatorFraction fraction =
                            new ProcedureDefinitionMutatorFraction(context);
                    fraction.init((ProcedureDefinitionMutator) mutator);
                    return fraction;
                }
            };

    private static final int VH_TYPE_ARGUMENT = 1;
    private static final int VH_TYPE_ADD = 2;
    private static final int NO_ORIGINAL_INDEX = -1;

    private static final String NEW_ARGUMENT_NAME = "x";

    private Context context;
    private BlocklyController mController;
    private NameManager mVariableNameManager;
    private ProcedureManager mProcedureManager;

    private String mProcedureName;
    private ArrayList<ArgInfo> mArgInfos = new ArrayList<>();  // Name, original index
    private boolean mHasStatementInput = true;

    private Adapter mAdapter;

    private TextField mActiveArgNameField = null;
    private ListContainer mRecycler;

    private String headerFormat = "";
    private String headerText = "";


    public ProcedureDefinitionMutatorFraction(Context context) {
        super(context);
        this.context = context;
    }

    private void init(ProcedureDefinitionMutator mutator) {
        mController = mutator.getBlock().getController();
        mVariableNameManager = mController.getWorkspace().getVariableNameManager();
        mProcedureManager = mController.getWorkspace().getProcedureManager();

        mProcedureName = mutator.getProcedureName();
        mHasStatementInput = mutator.hasStatementInput();

        List<String> arguments = mutator.getArgumentNameList();
        int count = arguments.size();
        mArgInfos.clear();
        mArgInfos.ensureCapacity(count);
        for (int i = 0; i < count; ++i) {
            mArgInfos.add(new ArgInfo(arguments.get(i), i));
        }
        onCreateDialog();
    }

    public CommonDialog onCreateDialog() {
        CommonDialog dialog = new CommonDialog(context);
        Component layout = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_procedure_definition_mutator_dialog, null, false);

        ResourceManager manager = context.getResourceManager();
        try {
            headerFormat = manager.getElement(ResourceTable.String_mutator_procedure_def_header_format).getString();
            headerText = String.format(headerFormat, mProcedureName);
        } catch (IOException | WrongTypeException | NotExistException e) {
            LogUtils.e(TAG, e.toString());
        }

        Text header = (Text) layout.findComponentById(ResourceTable.Id_mutator_procedure_def_header);
        header.setText(headerText);

        mRecycler = (ListContainer) layout.findComponentById(ResourceTable.Id_mutator_procedure_def_recycler);
        mAdapter = new Adapter();
        mRecycler.setItemProvider(mAdapter);

        dialog.setButton(1, String.valueOf(ResourceTable.String_mutator_done), new ClickedListener() {
            @Override
            public void onClick(IDialog iDialog, int i) {
                finishMutation();
            }
        });
        dialog.setTransparent(true);
        dialog.setContentCustomComponent(layout);
        return dialog;
    }

    private Component.ClickedListener mOnAddClick = new Component.ClickedListener() {
        @Override
        public void onClick(Component v) {
            int argPosition = mArgInfos.size();
            mArgInfos.add(new ArgInfo(NEW_ARGUMENT_NAME, NO_ORIGINAL_INDEX));
            mAdapter.notifyDataSetItemInserted(argPosition);
        }
    };

    private Component.ClickedListener mOnDeleteClick = new Component.ClickedListener() {
        @Override
        public void onClick(Component v) {
            int position = mArgInfos.indexOf(v.getTag()); // Set by onBindViewHolder(..)
            if (position >= 0) {
                mArgInfos.remove(position);
                mAdapter.notifyDataSetItemRemoved(position);
            }
        }
    };

    private final Component.FocusChangedListener mArgFocusChangeListener = new Component.FocusChangedListener() {
        @Override
        public void onFocusChange(Component v, boolean hasFocus) {
            TextField argNameField = (TextField) v;
            if (hasFocus) {
                mActiveArgNameField = argNameField;
            } else {
                validateAndApplyArgNameChange(argNameField);
                mActiveArgNameField = null;
            }
        }
    };

    private void validateAndApplyArgNameChange(TextField argNameField) {
        ArgInfo argInfo = (ArgInfo) argNameField.getTag();
        String newName = argNameField.getText();
        newName = mVariableNameManager.makeValidName(newName, null);
        if (newName != null && mVariableNameManager.getExisting(newName) == null) {
            argInfo.name = newName;
        }
    }

    private void finishMutation() {
        if (mActiveArgNameField != null) {
            validateAndApplyArgNameChange(mActiveArgNameField);
        }

        int count = mArgInfos.size();
        List<String> argNames = new ArrayList<>(count);
        List<ArgumentIndexUpdate> indexUpdates = null;
        for (int i = 0; i < count; ++i) {
            ArgInfo argInfo = mArgInfos.get(i);
            argNames.add(argInfo.name);
            if (argInfo.originalIndex != NO_ORIGINAL_INDEX) {
                if (indexUpdates == null) {
                    indexUpdates = new ArrayList<>();
                }
                indexUpdates.add(new ArgumentIndexUpdate(argInfo.originalIndex, i));
            }
        }

        mProcedureManager.mutateProcedure(mProcedureName,
                new ProcedureInfo(mProcedureName, argNames, mHasStatementInput),
                indexUpdates);
    }

    private static class ArgInfoViewHolder extends RecyclerAdapter.ViewHolder {
        Image mAddButton = null;
        TextField mArgName = null;
        Image mDeleteButton = null;

        public ArgInfoViewHolder(Component view, int viewType) {
            super(view);

            if (viewType == VH_TYPE_ADD) {
                mAddButton = (Image) view.findComponentById(ResourceTable.Id_procedure_argument_add);
            } else if (viewType == VH_TYPE_ARGUMENT) {
                mArgName = (TextField) view.findComponentById(ResourceTable.Id_procedure_argument_name);
                mDeleteButton = (Image) view.findComponentById(ResourceTable.Id_procedure_argument_delete);
            }
        }
    }

    private class Adapter extends RecyclerAdapter<ArgInfoViewHolder> {
        LayoutScatter mInflater;

        public Adapter() {
            mInflater = LayoutScatter.getInstance(context);
        }

        @Override
        public Object getItem(int i) {
            return i;
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public int getItemViewType(int position) {
            if (position == mArgInfos.size()) {
                return VH_TYPE_ADD;
            } else {
                return VH_TYPE_ARGUMENT;
            }
        }


        @Override
        public ViewHolder onCreateViewHolder(ComponentContainer parent, int viewType) {
            Component view;
            if (viewType == VH_TYPE_ADD) {
                view = mInflater.parse(ResourceTable.Layout_procedure_definition_mutator_add, null, false);
            } else if (viewType == VH_TYPE_ARGUMENT) {
                view = mInflater.parse(ResourceTable.Layout_procedure_definition_mutator_argument, null, false);
            } else {
                throw new IllegalStateException("Unrecognized view type " + viewType);
            }
            view.setLayoutConfig(new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT));
            return new ViewHolder(view);
        }

        @Override
        public int getItemCount() {
            return mArgInfos.size() + 1;
        }

        @Override
        public void onBindViewHolder(ArgInfoViewHolder holder, int position) {
            if (holder.getItemViewType() == VH_TYPE_ADD) {
                holder.mAddButton.setClickedListener(mOnAddClick);
            }
            if (holder.getItemViewType() == VH_TYPE_ARGUMENT) {
                ArgInfo arg = mArgInfos.get(position);
                holder.mArgName.setText(arg.name);
                holder.mArgName.setTag(arg);
                holder.mArgName.setFocusChangedListener(mArgFocusChangeListener);
                holder.mDeleteButton.setTag(arg);
                holder.mDeleteButton.setClickedListener(mOnDeleteClick);
            }
        }
    }

    private static final class ArgInfo {
        ArgInfo(String name, int originalPosition) {
            this.name = name;
            this.originalIndex = originalPosition;
        }

        String name;
        int originalIndex;  // or NO_ORIGINAL_INDEX
    }
}
